package com.wn.sddfp.system.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.wn.bank.access.api.TradeLogApi;
import com.wn.sddfp.common.model.dto.bank.BankCardAddDTO;
import com.wn.sddfp.common.model.dto.bank.CardBindDto;
import com.wn.sddfp.common.model.dto.bank.SignatureDto;
import com.wn.sddfp.common.model.dto.user.LoginDto;
import com.wn.sddfp.common.model.dto.user.UserInfoVo;
import com.wn.sddfp.common.model.dto.user.UserRegisterDto;
import com.wn.sddfp.common.model.exception.Assert;
import com.wn.sddfp.common.model.result.enums.impl.BusinessCode;
import com.wn.sddfp.common.model.utils.JwtTemplate;
import com.wn.sddfp.common.model.vo.LoginVo;
import com.wn.sddfp.system.entity.User;
import com.wn.sddfp.system.entity.UserCard;
import com.wn.sddfp.system.entity.UserDetail;
import com.wn.sddfp.system.mapper.UserMapper;
import com.wn.sddfp.system.service.IUserCardService;
import com.wn.sddfp.system.service.IUserDetailService;
import com.wn.sddfp.system.service.IUserService;
import com.wn.sddfp.system.utils.TokenUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author yellow docker
 * @since 2024-07-29
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private IUserDetailService userDetailService;

    //注入加密工具
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private IUserCardService userCardService;

    @Autowired
    private JwtTemplate jwtTemplate;

    @Autowired
    private TradeLogApi tradeLogApi;

    //进行注册的业务逻辑
    @Transactional  // 直接添加注解就可以
    @Override
    public void register(UserRegisterDto userRegisterDto) {

        /**
         * 业务逻辑:
         *   1、通过手机号码、用户名名判断用户是否存在
         *   2、保存用户 （用户名 + 密码 + 手机号）
         *
         *  操作表:
         *    1、添加用户
         *         操作2个表
         */

        //01 判断 手机号码、用户名
        User checkPhone = checkUserByPhone(userRegisterDto.getPhone());

        //判断 checkPhone 已存在 抛出异常
        Assert.error(checkPhone != null, BusinessCode.USER_PHONE_EXISTED);

        User checkUserName = checkUserByUserName(userRegisterDto.getUserName());

        //判断 checkUserName 已存在 抛出异常
        Assert.error(!Objects.isNull(checkUserName), BusinessCode.USER_EXISTED);
        //02 保存u_user 、u_user_detail
        saveUser(userRegisterDto);
    }

    /**
     * 保存用户
     * @param userRegisterDto
     */
    private void saveUser(UserRegisterDto userRegisterDto) {
        User u_user = new User();
        //设置手机号码
        u_user.setPhone(userRegisterDto.getPhone());
        //设置用户名
        u_user.setUserName(userRegisterDto.getUserName());
        //密码  设置加密 存储到mysql中
        u_user.setPwd(passwordEncoder.encode(userRegisterDto.getPwd()));
        //用户hutool 加密    后面的登录的方法 解密一定要 保持一致 不能随意
//        u_user.setPwd(BCrypt.hashpw(userRegisterDto.getPwd(), BCrypt.gensalt()));
        //设置 用户类型  个人用户1  企业用户2
        u_user.setUserType("1");
        //状态 默认0
        u_user.setUserStatus(false);

        save(u_user);

        UserDetail u_user_detail = new UserDetail();

        u_user_detail.setId(u_user.getId());
        u_user_detail.setUserName(u_user.getUserName());
        u_user_detail.setPhone(u_user.getPhone());
        //借款金额 0
        u_user_detail.setBorrowMoney(new BigDecimal("0"));
        //用户code  随机的字符串
        u_user_detail.setUserCode("UC_" + UUID.randomUUID().toString().substring(0,10));
        //绑卡状态 0
        u_user_detail.setCardBindStatus(false);
        userDetailService.save(u_user_detail);

    }

    @Override
    public UserInfoVo findUserById(Long userId) {

        //查询user
        User user = getById(userId);

        //查询 user_detail
        UserDetail userDetail = userDetailService.getOne(Wrappers.lambdaQuery(UserDetail.class).eq(
                UserDetail::getId,
                userId
        ));

        UserInfoVo userInfoVo = new UserInfoVo();
        userInfoVo.setUserName(user.getUserName());
        userInfoVo.setIdentityCode(user.getIdentityCode());
        userInfoVo.setUserCode(userDetail.getUserCode());

        return userInfoVo;
    }

    /**
     * 用户绑卡：
     * 1、从token中获取userId
     * 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
     *
     * 3、如果未绑卡，保存保卡信息到u_user_card表
     *
     * 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
     *
     * 5、生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
     *
     * 6、远程访问托管代理服务（暂不实现）
     *   业务逻辑:
     *       u_user_detail 表    CARD_BIND_STATUS 判断是否绑卡没有
     *       u_user_card   表    存储绑卡信息
     *            u_user_card 表中以存储绑卡信息
     *                   u_user        表中的字段进行更新 realName
     *                   u_user_detail 表中的字段进行更新 REQ_CODE
     *
     */
    @Override
    public SignatureDto addBankAccount(CardBindDto cardBindDto) {

        String userId = TokenUtils.getUserId();
        System.out.println("userId = " + userId);
        //根据用户id 查询user_detail 表 查看是否绑卡
        UserDetail userDetail = userDetailService.getOne(Wrappers.lambdaQuery(UserDetail.class).eq(UserDetail::getId, userId));
        //判断是否绑卡了
        Boolean cardBindStatus = userDetail.getCardBindStatus();
        //如果绑卡抛出异常
        if (cardBindStatus){
            Assert.error(BusinessCode.USER_CARD_ALREADY_BINDED);
        }
        //未绑卡  添加绑卡信息
        UserCard userCard = new UserCard();
        //添加银行卡号
        userCard.setCardNo(cardBindDto.getCardNo());
        //手机号
        userCard.setPhone(userDetail.getPhone());
        //用户id
        userCard.setUserId(userDetail.getId());
        //状态
        userCard.setStatus(false);   //其他的数据 会后面进行更新
        userCardService.save(userCard);

        //更新 用户表 (真实姓名、id)
        User user = new User();
        user.setRealName(cardBindDto.getRealName());
        user.setIdentityCode(cardBindDto.getIdentityCode());
        update(user,Wrappers.lambdaUpdate(User.class).eq(User::getId,userId));

        //更新userDetail的流水号
        String req_code = UUID.randomUUID().toString().replace("-","").substring(0, 10);
        userDetail.setReqCode(req_code);
        userDetailService.update(userDetail,Wrappers.lambdaUpdate(UserDetail.class).eq(UserDetail::getId,userId));

        //调用托管平台
        BankCardAddDTO bankCardAddDTO = new BankCardAddDTO();

        //流水码
        bankCardAddDTO.setReqCode(req_code);
        //用户名称
        bankCardAddDTO.setUserId(Long.valueOf(userId));
        //远程调用
        SignatureDto signatureDto = tradeLogApi.proxyBindCardLog(bankCardAddDTO);

        return signatureDto;
    }

    /**
     * getIsPhoneLogin() 如果是 true 就是 手机登录
     *                   否则是密码登录
     * @param loginDto
     * @return
     */
    @Override
    public LoginVo login(LoginDto loginDto) {
        LoginVo loginVo = loginDto.getIsPhoneLogin() ? loginByPhone(loginDto):loginByUserAndPwd(loginDto);

        //判断 不能为null
        Assert.error(Objects.isNull(loginVo),BusinessCode.USER_LOGIN_ERROR);

        return loginVo;
    }

    /**
     * 密码登录
     * @param loginDto
     * @return
     */
    private LoginVo loginByUserAndPwd(LoginDto loginDto) {

        //通过 用户名查询 User
        User user = getOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getUserName, loginDto.getLoginName()));

        //判断 一下
        Assert.error(Objects.isNull(user),BusinessCode.USER_LOGIN_ERROR);

        //拿到密码
        String dbPwd = user.getPwd();

        //校验密码 (自己是文明的密码、数据库是加密的密码)
        //matches()  会自动进行判断
        boolean isSuccess = passwordEncoder.matches(loginDto.getLoginSecret(),dbPwd);

        if (isSuccess){
            return getLoginVo(user);
        }
        return null;
    }

    /**
     *  把 user对象转成 loginVo 返回给前端
     * @param user
     * @return
     */
    private LoginVo getLoginVo(User user) {

        Map<String,Object> payload = new HashMap<>();
        payload.put("userId",user.getId());
        payload.put("phone",user.getPhone());
        //把载荷 通过工具生成 token
        String token = jwtTemplate.createToken(payload);

        stringRedisTemplate.opsForValue().set(
                "system:token:" + user.getId(),
                token,
                Duration.ofMinutes(30)  //存储30min
        );

        return LoginVo.builder().userName(user.getUserName())
                .id(user.getId())
                .phone(user.getPhone())
                .token(token).build();
    }

    /**
     * 手机登录
     * @param loginDto
     * @return
     */
    private LoginVo loginByPhone(LoginDto loginDto) {

        /**
         *  1、手机号码 和  短信进行登录
         *    判断用户是否存在   直接登录
         *    判断用户不否存在   自动注册用户 、进行登录
         */
        User user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getPhone, loginDto.getLoginName()));
        //校验 验证码
        //拿到 用户的 验证码 和redis 中的验证码进行比较
        String loginCode = loginDto.getLoginSecret();
        //这个key 和 存储到redis中的key需要保持一致
        String keyCode = "SMS:" + loginDto.getLoginName();
        String redisCode = stringRedisTemplate.opsForValue().get(keyCode);
        //判断
        Assert.error(StringUtils.isEmpty(redisCode),BusinessCode.USER_VERIFY_CODE_ERROR);
        //校验 验证码
        if (loginCode.equals(redisCode)){
            //登录成功
            if (Objects.isNull(user)){
                //校验 验证码
                //存储用户 自动注册
                UserRegisterDto userRegisterDto = new UserRegisterDto();
                //用户名可以随机 或者使用电话号码
                userRegisterDto.setUserName(loginDto.getLoginName());
                //默认密码 给 123456
                userRegisterDto.setPwd(passwordEncoder.encode("123456"));
                userRegisterDto.setPhone(loginDto.getLoginName());
                saveUser(userRegisterDto);
            }
            //保存完毕后 要返回user
            user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getPhone, loginDto.getLoginName()));

            return getLoginVo(user);

        }
        return null;
    }

    /**
     * 通过phone判断 用户是否存在
     * @param phone
     * @return
     */
    private User checkUserByPhone(String phone) {
        /**
         *    getOne()  是 UserService 中提供的方法
         *       Wrappers.lambdaQuery(User.class).eq(User::getPhone, phone) 条件查询
         */
        return getOne(Wrappers.lambdaQuery(User.class).eq(User::getPhone, phone));
    }

    /**
     *  通过 用户名查询用户
     * @param username
     * @return
     */
    private User checkUserByUserName(String username) {
        return  getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserName, username));
    }
}
